package com.tianji.promotion.service.impl;

import com.tianji.common.utils.CollUtils;
import com.tianji.common.utils.UserContext;
import com.tianji.promotion.discount.Discount;
import com.tianji.promotion.discount.DiscountStrategy;
import com.tianji.promotion.domain.dto.CouponDiscountDTO;
import com.tianji.promotion.domain.dto.OrderCouponDTO;
import com.tianji.promotion.domain.dto.OrderCourseDTO;
import com.tianji.promotion.domain.po.Coupon;
import com.tianji.promotion.domain.po.CouponScope;
import com.tianji.promotion.domain.utils.PermuteUtil;
import com.tianji.promotion.enums.UserCouponStatus;
import com.tianji.promotion.mapper.UserCouponMapper;
import com.tianji.promotion.service.ICouponScopeService;
import com.tianji.promotion.service.IDiscountService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
import java.util.function.Predicate;
import java.util.function.Supplier;
import java.util.stream.Collectors;

/**
 * @author Yandel
 * @date 2025/8/19 下午7:25
 * @description:
 */
@Service
@Slf4j
public class findDiscountSolutionImpl implements IDiscountService {

    @Resource
    private UserCouponMapper userCouponMapper;

    @Resource
    private ICouponScopeService couponScopeService;

    @Resource
    private Executor calculateCouponExecutor;

    @Override
    public List<CouponDiscountDTO> findDiscountSolution(List<OrderCourseDTO> orderCourses) {
        //1、查询当前用户可用的优惠券  coupon 和user_coupon  条件  userId,status =1
        List<Coupon> coupons = userCouponMapper.queryMyCoupons(UserContext.getUser());
        if (CollUtils.isEmpty(coupons)) {
            return CollUtils.emptyList();
        }

        log.info("用户的优惠券有coupons:{}", coupons);
        log.info("用户的优惠券有:{}张", coupons.size());
        for (Coupon coupon : coupons) {
            log.info("用户优惠券有1:{}", coupon);
        }
        //2.初筛
        int totalAmount = orderCourses.stream().mapToInt(OrderCourseDTO::getPrice).sum();
        //校验优惠券是否有用
        //第一种写法
        /* List<Coupon> availableCoupons = new ArrayList<>();
        for (Coupon coupon : coupons) {
            boolean flag = DiscountStrategy.getDiscount(coupon.getDiscountType()).canUse(totalAmount, coupon);
            if (flag) {
                availableCoupons.add(coupon);
            }
        }*/
        //第二种写法
        /*        List<Coupon> availableCoupons = coupons.stream().filter(new Predicate<Coupon>() {
            @Override
            public boolean test(Coupon coupon) {
                return DiscountStrategy.getDiscount(coupon.getDiscountType()).canUse(totalAmount, coupon);
            }
        }).collect(Collectors.toList());*/
        //第三种写法
        List<Coupon> availableCoupons = coupons.stream()//初筛的优惠券集合
                .filter(coupon -> DiscountStrategy.getDiscount(coupon.getDiscountType()).canUse(totalAmount, coupon))
                .collect(Collectors.toList());
        if (CollUtils.isEmpty(availableCoupons)) {
            return CollUtils.emptyList();
        }
        log.info("经过初筛选可用的优惠券有availableCoupons:{}", availableCoupons);
        log.info("经过初筛选可用的优惠券有:{}张", availableCoupons.size());
        for (Coupon coupon : availableCoupons) {
            log.info("用户优惠券有2:{}", coupon);
        }

        //3.细晒（需要考虑优惠券的限定范围）排列组合
        Map<Coupon, List<OrderCourseDTO>> avaMap = findAvailableCoupon(availableCoupons, orderCourses);
        if (CollUtils.isEmpty(avaMap)) {
            return CollUtils.emptyList();
        }


        Set<Map.Entry<Coupon, List<OrderCourseDTO>>> entries = avaMap.entrySet();
        for (Map.Entry<Coupon, List<OrderCourseDTO>> entry : entries) {
            log.info("细筛之后优惠券有：{},{}",
                    DiscountStrategy.getDiscount(entry.getKey().getDiscountType()).getRule(entry.getKey()),
                    entry.getKey());

            List<OrderCourseDTO> value = entry.getValue();
            for (OrderCourseDTO courseDTO : value) {
                log.info("可用课程:{}", courseDTO);
            }
        }
        availableCoupons = new ArrayList<>(avaMap.keySet());//这里才是真正可用的优惠券集合

        log.info("细筛之后的 优惠券个数:{}", availableCoupons.size());


        for (Coupon coupon : availableCoupons) {
            log.info("优惠券有:{}", DiscountStrategy
                    .getDiscount(coupon.getDiscountType()).getRule(coupon), coupon);
        }

        //排列组合
        List<List<Coupon>> solutions = PermuteUtil.permute(availableCoupons);
        for (Coupon availableCoupon : availableCoupons) {
            solutions.add(List.of(availableCoupon));//添加单券的方案
        }
        log.info("排列组合~");
        for (List<Coupon> solution : solutions) {
            List<Long> cids = solution.stream().map(Coupon::getId).collect(Collectors.toList());
            log.info("{}", cids);
        }
        /*
        [1664678776523046914, 1664679070791221225]
        [1664679070791221225, 1664678776523046914]
        [1664678776523046914]
        [1664679070791221225]
        */
        //4.计算每一种组合的优惠明细
       /* List<CouponDiscountDTO> dots = new ArrayList<>();
        for (List<Coupon> solution : solutions) {
            CouponDiscountDTO dto = calculateSolutionDiscount(avaMap, orderCourses, solution);
            dots.add(dto);
        }*/

        //5.使用多线程改造第4步并行计算每一种组合的优惠明细
        log.info("使用多线程计算  每一个种组合的优惠明细");
//        List<CouponDiscountDTO> dots = new ArrayList<>();//线程不安全
        List<CouponDiscountDTO> dots = Collections.synchronizedList(new ArrayList<>(solutions.size()));
        CountDownLatch latch = new CountDownLatch(solutions.size());
        for (List<Coupon> solution : solutions) {
            CompletableFuture.supplyAsync(new Supplier<CouponDiscountDTO>() {
                @Override
                public CouponDiscountDTO get() {
                    log.info("线程{} 开始计算方案 {}", Thread.currentThread().getName(),
                            solution.stream().map(Coupon::getId).collect(Collectors.toSet()));
                    CouponDiscountDTO dto = calculateSolutionDiscount(avaMap, orderCourses, solution);
                    return dto;
                }
            }, calculateCouponExecutor).thenAccept(new Consumer<CouponDiscountDTO>() {
                @Override
                public void accept(CouponDiscountDTO dto) {
                    log.info("方案最终优惠{}  方案中使用了 {} 规则 {}", dto.getDiscountAmount(), dto.getIds(), dto.getRules());
                    dots.add(dto);
                    latch.countDown();//计算减1
                }
            });
        }
        try {
            latch.await(2, TimeUnit.SECONDS);//主线程会最多阻塞2秒
        } catch (InterruptedException e) {
            log.info("优惠方案计算被中断，{}", e.getMessage());
            throw new RuntimeException(e);

        }


        //6.筛选最优解
        return findBestSolution(dots);
    }

    /**
     * 筛选出最优解
     *
     * @param dots
     * @return TODO 待理解
     */
    private List<CouponDiscountDTO> findBestSolution(List<CouponDiscountDTO> dots) {
        // 1.准备Map记录最优解
        Map<String, CouponDiscountDTO> moreDiscountMap = new HashMap<>();
        Map<Integer, CouponDiscountDTO> lessCouponMap = new HashMap<>();
        // 2.遍历，筛选最优解
        for (CouponDiscountDTO solution : dots) {
            // 2.1.计算当前方案的id组合
            String ids = solution.getIds().stream()
                    .sorted(Long::compare).map(String::valueOf).collect(Collectors.joining(","));
            // 2.2.比较用券相同时，优惠金额是否最大
            CouponDiscountDTO best = moreDiscountMap.get(ids);
            if (best != null && best.getDiscountAmount() >= solution.getDiscountAmount()) {
                // 当前方案优惠金额少，跳过
                continue;
            }
            // 2.3.比较金额相同时，用券数量是否最少
            best = lessCouponMap.get(solution.getDiscountAmount());
            int size = solution.getIds().size();
            if (size > 1 && best != null && best.getIds().size() <= size) {
                // 当前方案用券更多，放弃
                continue;
            }
            // 2.4.更新最优解
            moreDiscountMap.put(ids, solution);
            lessCouponMap.put(solution.getDiscountAmount(), solution);
        }
        // 3.求交集
        Collection<CouponDiscountDTO> bestSolutions = CollUtils
                .intersection(moreDiscountMap.values(), lessCouponMap.values());
        // 4.排序，按优惠金额降序
        return bestSolutions.stream()
                .sorted(Comparator.comparingInt(CouponDiscountDTO::getDiscountAmount).reversed())
                .collect(Collectors.toList());
    }

    /**
     * 计算每一个方案的  优惠明细
     *
     * @param avaMap   优惠券和可以用的课程映射集合
     * @param courses  订单中所有的课程
     * @param coupons  当前方案中的优惠券
     * @return
     */
    private CouponDiscountDTO calculateSolutionDiscount(Map<Coupon, List<OrderCourseDTO>> avaMap,
                                                        List<OrderCourseDTO> courses, List<Coupon> coupons) {
        //1.创建方案结果dto对象
        CouponDiscountDTO dto = new CouponDiscountDTO();
        //2.初始化商品id和商品折扣明细的映射，初始折扣明细全都设置为0
        Map<Long, Integer> detailMap = courses.stream().collect(Collectors.toMap(OrderCourseDTO::getId, oc -> 0));
        //3.计算该方案的优惠信息
        //3.1循环方案中优惠券
        for (Coupon coupon : coupons) {
            //3.2取出该优惠券对应的可用课程
            List<OrderCourseDTO> availableCourses = avaMap.get(coupon);
            //3.3计算可用课程的总金额（商品价格－该商品的折扣明细）
            int totalAmount = availableCourses.stream()
                    .mapToInt(value -> value.getPrice() - detailMap.get(value.getId())).sum();
            //3.4判断优惠券是否可用
            Discount discount = DiscountStrategy.getDiscount(coupon.getDiscountType());
            if (!discount.canUse(totalAmount, coupon)) {
                continue;//优惠券不可用，则跳过，则处理下一个优惠券
            }
            //3.5计算该优惠券使用后的折扣值
            int discountAmount = discount.calculateDiscount(totalAmount, coupon);
            //3.6更新商品的折扣明细（更新商品id的商品折扣明细）
            calculateDetailDiscount(detailMap, availableCourses, totalAmount, discountAmount);
            //3.7累加每一个优惠券的优惠金额赋值给方案结果dto对象
            dto.getIds().add(coupon.getId());//只要执行当前在这句话，表示该优惠券生效了
            dto.getRules().add(discount.getRule(coupon));
            dto.setDiscountAmount(discountAmount + dto.getDiscountAmount());//不能覆盖，应该是所有生效的优惠券累加的优惠金额
        }

        return dto;
    }

    /**
     * 计算商品  折扣券明细
     *
     * @param detailMap
     * @param availableCourses
     * @param totalAmount
     * @param discountAmount
     */
    private void calculateDetailDiscount(Map<Long, Integer> detailMap,
                                         List<OrderCourseDTO> availableCourses,
                                         int totalAmount, int discountAmount) {
        int times = 0;
        int remainDiscount = discountAmount;
        for (OrderCourseDTO course : availableCourses) {
            // 更新课程已计算数量
            times++;
            int discount = 0;
            // 判断是否是最后一个课程
            if (times == availableCourses.size()) {
                // 是最后一个课程，总折扣金额 - 之前所有商品的折扣金额之和
                discount = remainDiscount;
            } else {
                // 计算折扣明细（课程价格在总价中占的比例，乘以总的折扣） 
                discount = discountAmount * course.getPrice() / totalAmount;
                remainDiscount -= discount;
            }
            // 更新折扣明细
            detailMap.put(course.getId(), discount + detailMap.get(course.getId()));
        }

    }

    /**
     *
     *
     * @param availableCoupons 初筛之后的优惠券集合
     * @param orderCourses     订单中的课程集合
     * @return
     */
    private Map<Coupon, List<OrderCourseDTO>> findAvailableCoupon(
            List<Coupon> availableCoupons, List<OrderCourseDTO> orderCourses) {

        Map<Coupon, List<OrderCourseDTO>> map = new HashMap<>();
        //1、环遍历初筛后的优惠券集合
        for (Coupon coupon : availableCoupons) {
            //2.找出每一个优惠券的可用课程
            List<OrderCourseDTO> availableCourses = orderCourses;
            //2.1判断优惠券是否限定了范围coupon.specific为true
            if (coupon.getSpecific()) {
                //2.2查询限定范围查询coupon_scope表，条件coupon_id
                List<CouponScope> scopeList = couponScopeService.lambdaQuery()
                        .eq(CouponScope::getCouponId, coupon.getId()).list();

                //2.3得到限定范围的id集合
                List<Long> bizIds = scopeList.stream()
                        .map(CouponScope::getBizId)
                        .collect(Collectors.toList());

                //2.4从orderCourses订单中所有的课程集合   筛选  该范围内的课程
                availableCourses = orderCourses.stream()
                        .filter(OrderCourseDTO -> bizIds.contains(OrderCourseDTO.getCateId()))
                        .collect(Collectors.toList());
            }
            if (CollUtils.isEmpty(availableCourses)) {
                //没有可用课程，抛弃
                continue;
            }
            //3.计算该优惠券可用课程的总金额
            int totalAmount = availableCourses.stream().mapToInt(OrderCourseDTO::getPrice).sum();
            //4.判断该优惠券是否可用如果可用添加到map中
            Discount discount = DiscountStrategy.getDiscount(coupon.getDiscountType());
            if (discount.canUse(totalAmount, coupon)) {
                map.put(coupon, availableCourses);
            }
        }
        return map;
    }


    @Override
    public CouponDiscountDTO queryDiscountDetailByOrder(OrderCouponDTO orderCouponDTO) {
        //1、查询用户优惠券
        List<Long> userCouponIds = orderCouponDTO.getUserCouponIds();
        List<Coupon> coupons = userCouponMapper.queryMyCouponsByUserId(userCouponIds, UserCouponStatus.UNUSED);
        if (CollUtils.isEmpty(coupons)){
            return null;
        }
        // 2.查询优惠券对应课程 coupon  :  biz_id  =1:n
        Map<Coupon, List<OrderCourseDTO>> availableCoupon = findAvailableCoupon(coupons, orderCouponDTO.getCourseList());
        if (CollUtils.isEmpty(availableCoupon)){
            return null;
        }
        /**
         * 计算每一个方案的  优惠明细
         *
         * @param avaMap   优惠券和可以用的课程映射集合
         * @param courses  订单中所有的课程
         * @param coupons 排列的优惠券
         * @return
         */
        // 3.查询优惠券规则
        return calculateSolutionDiscount(availableCoupon, orderCouponDTO.getCourseList(), coupons);
    }
}
